#include "defines.h"
#include "serial.h"
#include "lib.h"
#include "xmodem.h"
#include "timer.h"

#define XMODEM_SOH 0x01
#define XMODEM_STX 0x02
#define XMODEM_EOT 0x04
#define XMODEM_ACK 0x06
#define XMODEM_NAK 0x15
#define XMODEM_CAN 0x18
#define XMODEM_EOF 0x1a

static int nak_interval = DEFAULT_NAK_INTERVAL;

int xmodem_set_nak_interval(int sec)
{
    if (sec == 0) {
        return nak_interval;
    } else if (1 <= sec && sec <= 26) {
        nak_interval = sec;
        return sec;
    } else {
        return -1;
    }
}

static int xmodem_wait()
{
    timeout_init(nak_interval);

    serial_send_byte(XMODEM_NAK);
    timeout_start();

    while (!(serial_is_error() || serial_is_recv_enable())) {
        if (is_timeout()) {
            serial_send_byte(XMODEM_NAK);
            timeout_start();
        }
    }

    timeout_end();

    return 0;
}

// 返り値。データ正常→ブロック番号、データ不整合→-1、シリアルエラー(続行不可)→-2
static int xmodem_read_block(char *buf)
{
    unsigned char block_num, check_sum;
    int i, c, error = 0;

    c = serial_recv_byte();
    if (c == -1) return -2;
    block_num = (unsigned char)c;

    c = serial_recv_byte();
    if (c == -1) return -2;
    if ((block_num ^ (unsigned char)c) != 0xff) error = 1;

    check_sum = 0;
    for (i = 0; i < XMODEM_BLOCK_SIZE; i++) {
        c = serial_recv_byte();
        if (c == -1) return -2;
        *buf++ = (unsigned char)c;
        check_sum += (unsigned char)c;
    }

    c = serial_recv_byte();
    if (c == -1) return -2;
    if (check_sum ^ (unsigned char)c) error = 1;

    return error ? -1 : (int)block_num;
}

long xmodem_recv(char *buf)
{
    int r, receiving = 0, c;
    long size = 0;
    unsigned char block_number = 1;

    while (1) {
        if (!receiving)
            xmodem_wait();

        c = serial_recv_byte();

        if (c == XMODEM_SOH) {
            r = xmodem_read_block(buf);
            receiving = 1;

            if (r >= 0) {
                if (r == block_number) {
                    serial_send_byte(XMODEM_ACK);
                    block_number++;
                    size += XMODEM_BLOCK_SIZE;
                    buf += XMODEM_BLOCK_SIZE;

                } else if ((unsigned char)r + 1 == block_number) { /* 次が欲しい */
                    serial_send_byte(XMODEM_ACK);

                } else { /* 回復不能 */
                    xmodem_can();
                    return -1;
                }

            } else if (r == -1) {
                serial_send_byte(XMODEM_NAK);

            } else { // r == -2
                xmodem_can();
                return -1;
            }

        } else if (c == XMODEM_EOT) {
            serial_send_byte(XMODEM_ACK);
            break;

        } else if (c == XMODEM_CAN) {
            return -1;

        } else if (c == -1) {
            xmodem_can();
        	return -1;

        } else {
            if (receiving) {
                xmodem_can();
                return -1;
            }

            if (!receiving && c == 'A') { // ユーザによるキャンセル
                return -2;
            }

            // 受信開始前の関係ない入力は読み飛ばす
        }
    }

    return size;
}

/* ACKを次ブロック受信時まで遅らす。受信が間に合わない場合用 */
#define ACK_DELAY 0

int xmodem_recv_block(char *buf, unsigned char block_number, int receiving)
{
    int bn, c;

#if ACK_DELAY
    if (receiving) {
        serial_send_byte(XMODEM_ACK);
    }
#endif

    while (1) {
        if (!receiving)
            xmodem_wait();

        c = serial_recv_byte();

        if (c == XMODEM_SOH) {
            bn = xmodem_read_block(buf);
            receiving = 1;

            if (bn >= 0) {
                if (bn == block_number) {
#if ! ACK_DELAY
                    serial_send_byte(XMODEM_ACK);
#endif
                    return XMODEM_BLOCK_SIZE;

                } else if ((unsigned char)bn + 1 == block_number) {
                    serial_send_byte(XMODEM_ACK);

                } else {
                    xmodem_can();
                    return -1;
                }

            } else if (bn == -1) {
                serial_send_byte(XMODEM_NAK);

            } else { // bn == -2
                xmodem_can();
                return -1;
            }

        } else if (c == XMODEM_EOT) {
            serial_send_byte(XMODEM_ACK);
            return 0;

        } else if (c == XMODEM_CAN) {
            return -1;

        } else if (c == -1) {
            xmodem_can();
            return -1;

        } else {
            if (receiving) {
                xmodem_can();
                return -1;
            }

            if (!receiving && c == 'A') {
                return -2;
            }
        }
    }
}

void xmodem_can() /* ハイパーターミナルは終わる。TeraTermはダメ */
{
    serial_send_byte(XMODEM_CAN);
    serial_send_byte(XMODEM_CAN);
}
